/*******************************************************************************
 * Copyright (c) 2000, 2013 IBM Corporation and others.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *     IBM Corporation - initial API and implementation
 *******************************************************************************/
package org.eclipse.che.jdt.javadoc;

import org.eclipse.che.jdt.dom.ASTNodes;

import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.ISafeRunnable;
import org.eclipse.core.runtime.OperationCanceledException;
import org.eclipse.core.runtime.SafeRunner;
import org.eclipse.jdt.core.ITypeRoot;
import org.eclipse.jdt.core.JavaModelException;
import org.eclipse.jdt.core.dom.AST;
import org.eclipse.jdt.core.dom.ASTNode;
import org.eclipse.jdt.core.dom.ASTParser;
import org.eclipse.jdt.core.dom.CompilationUnit;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * Provides a shared AST for clients. The shared AST is
 * the AST of the active Java editor's input element.
 *
 * @since 3.0
 */
public class ASTProvider {
    public static final  int     SHARED_AST_LEVEL              = AST.JLS8;
    public static final  boolean SHARED_AST_STATEMENT_RECOVERY = true;
    public static final  boolean SHARED_BINDING_RECOVERY       = true;
    private static final Logger  LOG                           = LoggerFactory.getLogger(ASTProvider.class);
    private static final String  DEBUG_PREFIX                  = "ASTProvider > "; //$NON-NLS-1$
    /**
     * Tells whether this class is in debug mode.
     * @since 3.0
     */
    private static final boolean DEBUG                         = false;

    /**
     * Checks whether the given Java element has accessible source.
     *
     * @param je the Java element to test
     * @return <code>true</code> if the element has source
     * @since 3.2
     */
    private static boolean hasSource(ITypeRoot je) {
        if (je == null || !je.exists())
            return false;

        try {
            return je.getBuffer() != null;
        } catch (JavaModelException ex) {
            LOG.error(ex.getMessage(), ex);
        }
        return false;
    }

    /**
     * Creates a new compilation unit AST.
     *
     * @param input the Java element for which to create the AST
     * @param progressMonitor the progress monitor
     * @return AST
     */
    public static CompilationUnit createAST(final ITypeRoot input, final IProgressMonitor progressMonitor) {
        if (!hasSource(input))
            return null;

        if (progressMonitor != null && progressMonitor.isCanceled())
            return null;

        final ASTParser parser = ASTParser.newParser(SHARED_AST_LEVEL);
        parser.setResolveBindings(true);
        parser.setStatementsRecovery(SHARED_AST_STATEMENT_RECOVERY);
        parser.setBindingsRecovery(SHARED_BINDING_RECOVERY);
        parser.setSource(input);

        if (progressMonitor != null && progressMonitor.isCanceled())
            return null;

        final CompilationUnit root[]= new CompilationUnit[1];

        SafeRunner.run(new ISafeRunnable() {
            public void run() {
                try {
                    if (progressMonitor != null && progressMonitor.isCanceled())
                        return;
                    if (DEBUG)
                        System.err.println(getThreadName() + " - " + DEBUG_PREFIX + "creating AST for: " +
                                           input.getElementName()); //$NON-NLS-1$ //$NON-NLS-2$
                    root[0] = (CompilationUnit)parser.createAST(progressMonitor);

                    //mark as unmodifiable
                    ASTNodes.setFlagsToAST(root[0], ASTNode.PROTECT);
                } catch (OperationCanceledException ex) {
                    return;
                }
            }

            public void handleException(Throwable ex) {
                LOG.error(ex.getMessage(), ex);
            }
        });
        return root[0];
    }

    private static String getThreadName() {
        String name= Thread.currentThread().getName();
        if (name != null)
            return name;
        else
            return Thread.currentThread().toString();
    }
}
